package fxbattle.client.coms;

import java.awt.Color;
import java.io.IOException;

import javax.swing.SwingUtilities;

import fxbattle.client.model.*;
import fxbattle.protos.ServerProtos;
import fxbattle.protos.SharedProtos.ProtoLocation;
import fxbattle.protos.SharedProtos.Path;
import fxbattle.protos.SharedProtos.RgbColor;
import fxbattle.protos.ClientProtos.SetPath;
import fxbattle.protos.ServerProtos.FlowPathChange;
import fxbattle.protos.ServerProtos.NodeChange;
import fxbattle.protos.ServerProtos.NodeOccupationChanged;
import fxbattle.protos.ServerProtos.PathAvailabilityChanged;
import fxbattle.protos.ServerProtos.StateUpdate;


public class BattleMapSocketUpdater {
	
	private final BattleMap fMap;
	private final String fServer;
	private final int fPort;
	private ClientToServerSocket fSocket = null;
	
	public BattleMapSocketUpdater(BattleMap iMap, String iServer, int iPort) {
		fMap = iMap;
		fServer = iServer;
		fPort = iPort;
	}
	
	public Player requestToPlay(String iName) throws IOException {
		if(fSocket == null){
			fSocket = new ClientToServerSocket(fServer, fPort, this);
            ServerProtos.RespondToPlayRequest lResponse = fSocket.requestToPlay(iName);
            System.out.println("Response: " + lResponse);
            if(lResponse.hasReason()){
                throw new IOException(lResponse.getReason());
            }
			return asPlayer(lResponse.getYou());
		} else {
			throw new IOException("You're already connected!");
		}
	}
	
	public void retrieveInitialState(){
        ServerProtos.StateUpdate lState = fSocket.listenForInitialState();
        System.out.println("Initial State:");
        System.out.println(lState);
		_updateState(lState);
	}
	
	public void beginListening(){
		fSocket.beginListeningForUpdates();
	}
	
	public void failureMessage(final String iMessage){
		SwingUtilities.invokeLater(new Runnable(){
			public void run() {
                System.out.println(iMessage);
//				JOptionPane.showMessageDialog(null, iMessage);
			}
		});
	}
	
	public void sendRequest(Location iOrigin, Location iDest, boolean iIsActive){
		Path lPath = Path.newBuilder().setDestination(fromLocation(iDest)).setOrigin(fromLocation(iOrigin)).build();
		SetPath lRequest = SetPath.newBuilder().setIsActive(iIsActive).setToChange(lPath).build();
		fSocket.requestPathChange(lRequest);
	}

	public void updateState(final StateUpdate nextUpdate) {
		SwingUtilities.invokeLater(new Runnable(){
			public void run() {
				_updateState(nextUpdate);
			}
		});
	}
	
	private void _updateState(StateUpdate nextUpdate) {
//        System.out.println("Updating State to include: " + nextUpdate);
		for(NodeChange lNodeUpdate: nextUpdate.getNodeChangeList()){
			updateHex(lNodeUpdate);
		}
		for(PathAvailabilityChanged lAvailability: nextUpdate.getPathChangeList()){
			updatePathAvailability(lAvailability);
		}
		for(NodeOccupationChanged lOccupationChange: nextUpdate.getArmyChangeList()){
			updateOccupation(lOccupationChange);
		}
		for(FlowPathChange lFlowChange: nextUpdate.getFlowChangeList()){
			updateFlowPath(lFlowChange);
		}
	}
	
	private void updateFlowPath(FlowPathChange flowChange) {
		BattleHex lOrigin = asHex(flowChange.getPath().getOrigin());
		BattleHex lDest = asHex(flowChange.getPath().getDestination());
		Direction lDirFromOriginToDest = lOrigin.getLocation().getDirectionTo(lDest.getLocation());
		if(lDirFromOriginToDest == null){
			failureMessage("Couldn't go from " + lOrigin + " to " + lDest + " in one step.");
			return;
		}
		lOrigin.setFlowPathActivity(lDirFromOriginToDest, flowChange.getIsFlowing());
	}

	private void updateOccupation(NodeOccupationChanged occupationChange) {
		BattleHex lHex = asHex(occupationChange.getLoc());
		lHex.conquered(asArmy(occupationChange));
	}
	

	private void updatePathAvailability(PathAvailabilityChanged availability) {
		Location lOriginLoc = asLocation(availability.getToChange().getOrigin());
		Location lDestLoc = asLocation(availability.getToChange().getDestination());
		Direction lDirFromOriginToDest = lOriginLoc.getDirectionTo(lDestLoc);
		if(lDirFromOriginToDest == null){
			failureMessage("Couldn't go from " + lOriginLoc + " to " + lDestLoc + " in one step.");
			return;
		}
		BattleHex lOriginHex = ensureHex(lOriginLoc);
		BattleHex lDestHex = ensureHex(lDestLoc);
		// TODO this is a hack. It should be fixed eventually.
		if(availability.getIsAvailable()){
			lOriginHex.setAdjacentHex(lDirFromOriginToDest, lDestHex);
			lDestHex.setAdjacentHex(lDirFromOriginToDest.opposite(), lOriginHex);
		} else {
			lOriginHex.setAdjacentHex(lDirFromOriginToDest, BoundaryHex.getBoundary(lDestLoc));
			lDestHex.setAdjacentHex(lDirFromOriginToDest.opposite(), BoundaryHex.getBoundary(lOriginLoc));
		}
	}
	
	private void updateHex(NodeChange iUpdate){
		Location lHexLocation = asLocation(iUpdate.getLoc());
//		if(iUpdate.getIsPresent()){
			ensureHex(lHexLocation);
//		} else {
//			fMap.setHex(BoundaryHex.getBoundary(lHexLocation));
//		}
        if(iUpdate.getElevation() != 0){
            System.out.print(iUpdate);
            System.out.println();
        } else {
            System.out.print(".");
        }
		fMap.getHex(lHexLocation).setElevation(iUpdate.getElevation());
		fMap.getHex(lHexLocation).setIsBase(iUpdate.getType().getKind() == ServerProtos.NodeType.KindOfNode.BASE);
	}
	
	private BattleHex ensureHex(Location iLoc){
		if( ! fMap.hexExists(iLoc)){
			System.out.println("Adding Hex");
			fMap.setHex(new BattleHex(iLoc));
		}
		return fMap.getHex(iLoc);
	}
	
	private static Location asLocation(ProtoLocation iLocation){
		return Location.getLocation(iLocation.getLocX(), iLocation.getLocY());
	}
	
	private static ProtoLocation fromLocation(Location iLoc){
		return ProtoLocation.newBuilder().setLocX(iLoc.getX()).setLocY(iLoc.getY()).build();
	}
	
	private BattleHex asHex(ProtoLocation iLocation){
		return ensureHex(asLocation(iLocation));
	}
	
	private static Army asArmy(NodeOccupationChanged occupationChange) {
		if(occupationChange.hasOwner()){
			return new Army(asPlayer(occupationChange.getOwner()), occupationChange.getSize());
		} else {
			return Army.no_army;
		}
	}
	
	private static Player asPlayer(fxbattle.protos.ServerProtos.Player iPlayer){
		if(iPlayer == null){
			throw new NullPointerException("The player was null. That's a no no.");
		}
		return Player.getPlayer(iPlayer.getName(), asColor(iPlayer.getColor()));
	}
	
	private static Color asColor(RgbColor iColor){
		return new Color(iColor.getRed(), iColor.getGreen(), iColor.getBlue());
	}
	
}
